package com.dycx.system.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.JWTValidator;
import cn.hutool.jwt.signers.JWTSignerUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dycx.common.RestResponse;
import com.dycx.common.context.UserInfo;
import com.dycx.common.context.UserInfoContext;
import com.dycx.common.enums.SourceEnum;
import com.dycx.system.constants.AdminConstants;
import com.dycx.system.dto.AdminDTO;
import com.dycx.system.entity.AdminDO;
import com.dycx.system.enums.AdminTypeEnum;
import com.dycx.system.service.*;
import lombok.AllArgsConstructor;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;


@Service()
@AllArgsConstructor
public class AuthImpl implements AuthService {

    private RoleService roleService;
    private AdminRoleService adminRoleService;
    private AdminService adminService;
    private RolePermissionService roleActionService;
    private StringRedisTemplate stringRedisTemplate;
    private PermissionService permissionService;

    /**
     * 后台登录
     *
     * @param adminDto
     * @return
     */
    @Override
    public String login(AdminDTO adminDto) {
        //验证登录次数
        String loginNumKey = "AdminLoginNum:" + adminDto.getUsername();
        String adminLoginNumStr = stringRedisTemplate.opsForValue().get(loginNumKey);
        int adminLoginNum = StringUtils.isBlank(adminLoginNumStr) ? 1 : Integer.parseInt(adminLoginNumStr) + 1;
        if (adminLoginNum > 10) {
            throw new RuntimeException("连续登录失败超过10次，请5分钟后再试");
        }

        stringRedisTemplate.opsForValue().set(loginNumKey, Integer.toString(adminLoginNum), 5 * 60, TimeUnit.SECONDS);

        //查询用户信息
        LambdaQueryWrapper<AdminDO> wrapper = new LambdaQueryWrapper<>();
        wrapper.and(o -> {
            o.eq(AdminDO::getUsername, adminDto.getUsername())
                    .or()
                    .eq(AdminDO::getMobile, adminDto.getUsername());
        });
        wrapper.eq(AdminDO::getPassword, DigestUtils.md5Hex(adminDto.getPassword() + AdminConstants.PASSWORD_SALT));
        AdminDO adminDO = adminService.getOne(wrapper);
        Assert.notNull(adminDO, "用户名或密码不正确");

        if (!adminDO.getStatus()) {
            throw new RuntimeException("账户已被禁止登录");
        }

        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(adminDO.getId());
        userInfo.setUsername(adminDO.getUsername());
        userInfo.setSource(SourceEnum.SAAS.getCode());
        userInfo.setAdminType(adminDO.getUserType());

        //获取用户角色idArr
        List<String> allAction = roleService.getAllRolePermissionIds();
        if (AdminTypeEnum.SUPER_ADMIN.getCode().equals(adminDO.getUserType())) {
            userInfo.setRolePermissionIds(allAction);
        } else {
            List<Integer> roleIds = adminRoleService.getIdArr(adminDO.getId());
            if (roleIds.size() > 0) {
                List<String> actionArr = roleActionService.getPermissionIdsByRoleIds(roleIds);
                if (actionArr.size() > 0) {
                    actionArr.retainAll(allAction);
                    userInfo.setRolePermissionIds(actionArr);
                }
            }
        }

        //创建token
        String token = JWT.create()
                .addPayloads(BeanUtil.toBean(userInfo, Map.class))
                .setExpiresAt(DateUtil.offsetSecond(DateUtil.date(), AdminConstants.LOGIN_TIME))
                .sign(JWTSignerUtil.hs256(AdminConstants.JWT_KEY.getBytes()));//签名

        //登录日志传输操作人信息
        UserInfoContext.set(userInfo);

        //重置登录次数
        stringRedisTemplate.delete(loginNumKey);

        adminDto.setId(userInfo.getUserId());
        return token;
    }

    /**
     * 验证是否有接口访问权限
     *
     * @param request
     * @return
     */
    @Override
    public RestResponse<String> auth(HttpServletRequest request) {

        //登录页无需登录
        String router = request.getRequestURI();
        if (router.equals("/manage/system/admin/login")) return RestResponse.success();

        //令牌建议是放在请求头中，获取请求头中令牌
        String token = request.getHeader("X-Token");
        String source = request.getHeader("source");
        try {
            JWTUtil.verify(token, AdminConstants.JWT_KEY.getBytes());//验证令牌
            JWTValidator.of(token).validateDate(DateUtil.date()); //验证是否过期
        } catch (Exception e) {
            return RestResponse.notLogin("登录无效或过期");
        }

        //读取信息
        final JWT jwt = JWTUtil.parseToken(token);
        UserInfo userInfo = BeanUtil.toBean(jwt.getPayloads(), UserInfo.class);
        userInfo.setSource(Convert.toInt(source));

        //无需验证接口权限
        ArrayList<String> cancelAuth = new ArrayList<>();
        cancelAuth.add("/manage/system/admin/logout");
        cancelAuth.add("/manage/system/admin/updatePassword");
        cancelAuth.add("/manage/system/admin/getInfo");

        //非超管验证接口权限
        if (!userInfo.getAdminType().equals(AdminTypeEnum.SUPER_ADMIN.getCode()) && !cancelAuth.contains(router)) {
            if (ObjectUtil.isEmpty(userInfo.getRolePermissionIds())) return RestResponse.fail("没有操作权限");

            List<String> actionIds = roleService.getActionIdByRouter(permissionService.getAllList(), router);
            if (ObjectUtil.isEmpty(actionIds)) return RestResponse.fail("没有操作权限");

            //用户权限id与接口权限id是否有交集
            boolean hasIntersection = userInfo.getRolePermissionIds().stream().anyMatch(actionIds::contains);
            if (!hasIntersection) return RestResponse.fail("没有操作权限");
        }

        //单进程存储登录信息，后面调用
        UserInfoContext.set(userInfo);


        //即将过期2小时内重新生成token
        String newToken = null;
        if ((LocalDateTimeUtil.toEpochMilli(LocalDateTime.now()) / 1000) > userInfo.getExp() - AdminConstants.LOGIN_TIME / 2) {
            //创建token
            newToken = JWT.create()
                    .addPayloads(BeanUtil.toBean(userInfo, Map.class))
                    .setExpiresAt(DateUtil.offsetSecond(DateUtil.date(), AdminConstants.LOGIN_TIME))
                    .sign(JWTSignerUtil.hs256(AdminConstants.JWT_KEY.getBytes()));//签名
        }

        return RestResponse.success(newToken);
    }
}
